/*
 * Copyright 2007-2009 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package net.paoding.rose.web.impl.thread;

import java.lang.reflect.Method;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import net.paoding.rose.Rc;
import net.paoding.rose.web.Invocation;
import net.paoding.rose.web.impl.module.Module;
import net.paoding.rose.web.var.Flash;
import net.paoding.rose.web.var.FlashImpl;
import net.paoding.rose.web.var.Model;
import net.paoding.rose.web.var.ModelImpl;
import net.paoding.rose.web.var.PrivateVar;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.web.context.WebApplicationContext;

/**
 * 
 * @author qieqie.wang@paoding.net
 * 
 */
public class InvocationBean implements Invocation {
	private static final Log logger = LogFactory.getLog(InvocationBean.class);

	private ActionEngine actionEngine;

	private Object[] methodParameters;

	private Map<String, Object> attributes;

	private HttpServletRequest request;

	private HttpServletResponse response;
	
	private RequestPath requestPath;

	private MatchResult controllerMatchResult;
	
	private MatchResult actionMatchResult;
	
	private Module module;
	
	private Map<String, Object> attributesSnapshot;
	
	private boolean multiPartRequest;
	
	public InvocationBean() {
	}
	
	public Object invoke() throws Throwable {
		return this.actionEngine.invoke(this);
	}
	
	@Override
	public WebApplicationContext getApplicationContext() {
		return Rc.getWebApplicationContext(getModule().getPath());
	}

	public void setMethodParameters(Object[] methodParameters) {
		this.methodParameters = methodParameters;
	}

	@Override
	public Object getController() {
		return actionEngine.getControllerEngine().getController();
	}

	@Override
	public Class<?> getControllerClass() {
		return actionEngine.getControllerEngine().getControllerClass();
	}

	@Override
	public Method getMethod() {
		return actionEngine.getMethod();
	}

	@Override
	public String[] getMethodParameterNames() {
		String[] copy = new String[actionEngine.getParameterNames().length];
		System.arraycopy(actionEngine.getParameterNames(), 0, copy, 0, copy.length);
		return copy;
	}

	@Override
	public Object[] getMethodParameters() {
		Object[] copy = new Object[methodParameters.length];
		System.arraycopy(methodParameters, 0, copy, 0, copy.length);
		return copy;
	}
	
	@Override
	public Object getMethodParameter(String name) {
		String[] names = actionEngine.getParameterNames();
		for(int i = 0; i < names.length; i ++) {
			if (name != null && name.equals(names[i])) {
				return methodParameters[i];
			}
		}
		return null;
	}
	
	@Override
	public Object getParameter(String name) {
		Object value = getMethodParameter(name);
		if (value == null) {
			value = actionMatchResult.getParameter(name);
		}
		if (value == null) {
			value = controllerMatchResult.getParameter(name);
		}
		if (value == null) {
			value = request.getParameter(name);
		}
		return value;
	}

	@Override
	public void changeMethodParameter(int index, Object value) {
		if (value != this.methodParameters[index]) {
			if (logger.isDebugEnabled()) {
				logger.debug("change method parameter "
						+ this.actionEngine.getParameterNames()[index] + " (index=" + index
						+ ") from '" + this.methodParameters[index] + "' to '" + value + "'");
			}
			this.methodParameters[index] = value;
		}
	}

	public void changeMethodParameter(String name, Object value) {
		String[] names = this.actionEngine.getParameterNames();
		for (int i = 0; i < names.length; i++) {
			if (name.equals(names[i])) {
				changeMethodParameter(i, value);
				return;
			}
		}
	}

	@Override
	public Object getAttribute(String name) {
		if (attributes == null) {
			return null;
		}
		return attributes.get(name);
	}

	@Override
	public void removeAttribute(String name) {
		if (attributes != null) {
			attributes.remove(name);
		}
	}

	@Override
	public Set<String> getAttributeNames() {
		if (attributes == null) {
			return Collections.emptySet();
		}
		return attributes.keySet();
	}

	@Override
	public Invocation setAttribute(String name, Object value) {
		if (attributes == null) {
			attributes = new HashMap<String, Object>();
		}
		attributes.put(name, value);
		return this;
	}

	@Override
	public Model getModel() {
		Model model = (Model) getRequest().getAttribute("$$paoding-rose.model");
		if (model == null) {
			model = new ModelImpl(this);
			getRequest().setAttribute("$$paoding-rose.model", model);
		}
		return model;
	}

	@Override
	public Flash getFlash() {
		return getFlash(true);
	}
	
	public Flash getFlash(boolean create) {
		Flash flash = (Flash) getRequest().getAttribute("$$paoding-rose.flash");
		if (flash == null && create) {
			flash = new FlashImpl(this);
			getRequest().setAttribute("$$paoding-rose.flash", flash);
		}
		return flash;
	}
	
	@Override
	public RequestPath getRequestPath() {
		return requestPath;
	}

	@Override
	public HttpServletRequest getRequest() {
		return request;
	}

	@Override
	public HttpServletResponse getResponse() {
		return response;
	}

	public ControllerEngine getControllerEngine() {
		return getActionEngine().getControllerEngine();
	}
	
	public ActionEngine getActionEngine() {
		return actionEngine;
	}

	public void setActionEngine(ActionEngine actionEngine) {
		this.actionEngine = actionEngine;
	}

	public MatchResult getControllerMatchResult() {
		return controllerMatchResult;
	}

	public void setControllerMatchResult(MatchResult controllerMatchResult) {
		this.controllerMatchResult = controllerMatchResult;
	}

	public MatchResult getActionMatchResult() {
		return actionMatchResult;
	}

	public void setActionMatchResult(MatchResult actionMatchResult) {
		this.actionMatchResult = actionMatchResult;
	}

	public void setRequest(HttpServletRequest request) {
		this.request = request;
		PrivateVar.request(request);
		PrivateVar.setInvocation(this);
	}

	public void setResponse(HttpServletResponse response) {
		this.response = response;
	}

	public void setRequestPath(RequestPath requestPath) {
		this.requestPath = requestPath;
	}

	public Module getModule() {
		return module;
	}
	
	public void setModule(Module module) {
		this.module = module;
	}
	
	public Map<String, Object> getAttributesSnapshot() {
		return attributesSnapshot;
	}

	public void setAttributesSnapshot(Map<String, Object> attributesSnapshot) {
		this.attributesSnapshot = attributesSnapshot;
	}
	
	public void setMultiPartRequest(boolean multiPartRequest) {
		this.multiPartRequest = multiPartRequest;
	}
	
	public boolean isMultiPartRequest() {
		return multiPartRequest;
	}

}
